import os
import json
import time

try:
    import numpy as _np
except Exception:
    _np = None


def _to_builtin(obj):
    if _np is not None:
        if isinstance(obj, _np.generic):
            return obj.item()
        if isinstance(obj, _np.ndarray):
            return obj.tolist()
    if isinstance(obj, (bytes, bytearray)):
        try:
            return obj.decode("utf-8", errors="ignore")
        except Exception:
            return obj.hex()
    if isinstance(obj, (str, int, float, bool)) or obj is None:
        return obj
    if isinstance(obj, dict):
        return {str(_to_builtin(k)): _to_builtin(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple, set)):
        return [_to_builtin(x) for x in list(obj)]
    # pygame vectors (duck type)
    if hasattr(obj, "x") and hasattr(obj, "y") and hasattr(obj, "__iter__"):
        try:
            return [obj.x, obj.y] if not hasattr(obj, "z") else [obj.x, obj.y, obj.z]
        except Exception:
            pass
    try:
        return str(obj)
    except Exception:
        return None


class ReplayRecorder:
    def __init__(self, base_dir: str = "data/replay"):
        self.base_dir = base_dir
        self.fp = None
        self.path = None
        os.makedirs(self.base_dir, exist_ok=True)

    def start(self, run_name: str | None = None):
        if self.fp:
            return self.path
        if not run_name:
            run_name = time.strftime("run-%Y%m%d-%H%M%S")
        self.path = os.path.join(self.base_dir, f"{run_name}.jsonl")
        self.fp = open(self.path, "w", encoding="utf-8")
        return self.path

    def write_frame(self, game_state: dict, stats: dict | None = None):
        if not self.fp:
            self.start()
        obj = {
            "type": "frame",
            "game_state": _to_builtin(game_state or {}),
            "stats": _to_builtin(stats or {}),
            "t": time.time(),
        }
        self.fp.write(json.dumps(obj, ensure_ascii=False) + "\n")

    def close(self):
        if self.fp:
            try:
                self.fp.close()
            finally:
                self.fp = None
                self.path = None